﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

namespace Nirvana
{
    public class TexturePool : CSharpSingletion<TexturePool>
    {
        public struct TexturePoolEvent
        {
            public TexturePoolEvent(AssetID assetId, Action<Texture> complete)
            {
                this.assetId = assetId;
                this.complete = complete;
            }
            public AssetID assetId;
            public Action<Texture> complete;
        }
        private static Logger logger = LogSystem.GetLogger("TexturePool");
        private Dictionary<AssetID, TextureCache> textureAssetIdMap = new Dictionary<AssetID, TextureCache>();
        private Dictionary<Texture, TextureCache> textureMap = new Dictionary<Texture, TextureCache>();
        private Queue<TexturePoolEvent> queue = new Queue<TexturePoolEvent>();
        private int defaultMaxPoolCount;
        private int releaseCount;
        private float curLoadTime;
        private int curLoadingCount = 0;
        private int maxLoadingCount = 2;
        public int MaxLoadingCount
        {
            get { return maxLoadingCount; }
            set { maxLoadingCount = value; }
        }

        private float defaultReleaseAfterFree = 30f;
        public float DefaultReleaseAfterFree
        {
            get
            {
                return defaultReleaseAfterFree;
            }
            set
            {
                defaultReleaseAfterFree = value;
                foreach (var p in textureAssetIdMap)
                    p.Value.SetReleaseTimeAfterFree(value);
            }
        }
        public Dictionary<AssetID, TextureCache> GetCacheAssetIDMap()
        {
            return textureAssetIdMap;
        }

        protected override void Init()
        {
            Scheduler.AddFrameListener(Remove);
        }

        private void Remove()
        {
            this.LoadOnQueue();
            if (Time.time - curLoadTime >= 1f)
            {
                this.curLoadTime = Time.time;
                this.releaseCount = 0;
                this.textureAssetIdMap.RemoveAll(UnloadOnTime);
            }
        }
        private void LoadOnQueue()
        {
            //int num = this.maxLoadingCount + this.queue.Count / 5;
            //while (this.queue.Count > 0 && num - this.curLoadingCount > 0)
            if (this.queue.Count > 0)
            {
                this.curLoadingCount++;
                TexturePoolEvent _event = this.queue.Dequeue();
                Scheduler.RunCoroutine(this.LoadTextureASyncByLoadingCount(_event.assetId, _event.complete));
            }
        }

        public void Free(Texture texture, bool destroy = false)
        {
            if (texture == null)
            {
                logger.LogError("Try to free a null Texture.");
                return;
            }
            TextureCache textureCache;
            if (!this.textureMap.TryGetValue(texture, out textureCache))
            {
                logger.LogWarning("Try to free an instance {0} not allocated by this pool.", texture.name);
                return;
            }
            textureCache.Release();
            if (destroy && textureCache.ReferenceCount == 0)
            {
                AssetID assetID = textureCache.AssetID;
                var obj = textureCache.GetObject();
                if (obj != null)
                {
                    Resources.UnloadAsset(obj);
                    AssetManager.UnloadAsseBundle(assetID.BundleName);
                    this.textureMap.Remove(obj);
                }
                this.textureAssetIdMap.Remove(assetID);
            }
        }
        public bool Retain(Texture texture)
        {
            if (texture == null)
            {
                Debug.Log("Try to Retain a null Texture.");
                return false;
            }

            if (!this.textureMap.TryGetValue(texture, out TextureCache cache))
            {
                return false;
            }

            cache.Retain();
            return true;
        }
        public void Clear()
        {
            this.textureAssetIdMap.Clear();
            this.textureMap.Clear();
            this.queue.Clear();
            curLoadingCount = 0;
        }

        public void ClearAllUnused()
        {
            this.textureAssetIdMap.RemoveAll(Unload);
            this.queue.Clear();
            curLoadingCount = 0;
        }
        public void SetTexture(RawImage image, Texture texture)
        {
            if (image == null || texture == null) return;
            image.texture = texture;
            TextureReferenceHolder sh = image.GetOrAddComponent<TextureReferenceHolder>();
            sh.SetTexture(texture);
            //TextureReference sr = sh.Get();
            //if (sr == null)
            //{
            //    sr = new TextureReference();
            //}
            //sr.SetTexture(texture);
            //sh.Set(sr);

        }

        public void SetTexture(Renderer renderer, Texture texture)
        {
            if (renderer == null || texture == null) return;
            renderer.material.mainTexture = texture;
            TextureReferenceHolder sh = renderer.GetOrAddComponent<TextureReferenceHolder>();
            sh.SetTexture(texture);
            //TextureReference sr = sh.Get();
            //if (sr == null)
            //{
            //    sr = new TextureReference();
            //}
            //sr.SetTexture(texture);
            //sh.Set(sr);

        }
		        /// <summary>
        /// 加载Texture
        /// </summary>
        /// <param name="assetID"></param>
        /// <param name="complete"></param>
        /// <param name="isSync">是否立即加载，不是的话排队，由加载队列加载</param>
        public void Load(AssetID assetID, Action<Texture> complete, bool isSync = false)
        {
            if (AssetManager.Simulator.SimulateAssetBundle)
            {
                Scheduler.RunCoroutine(this.LoadTextureASync(assetID, complete));
                return;
            }
            assetID.AssetName = assetID.AssetName.ToLower();
            if (isSync)
            {
                Scheduler.RunCoroutine(this.LoadTexture(assetID, complete));
                return;
            }
            if (!AssetManager.IsVersionCached(assetID.BundleName))
            {
                Scheduler.RunCoroutine(this.LoadTextureASync(assetID, complete));
                return;
            }
            TexturePoolEvent item = new TexturePoolEvent(assetID, complete);
            this.queue.Enqueue(item);
        }

        private IEnumerator LoadTextureASyncByLoadingCount(AssetID assetid, Action<Texture> cb)
        {
            WaitLoadTexture wait = this.WaitLoadTexture(assetid, false);
            if (wait.keepWaiting)
            {
                yield return wait;
            }
            curLoadingCount--;
            if (curLoadingCount < 0)
            {
                Debug.LogError("[TexturePool] loadingCount is occur error " + this.curLoadingCount);
            }
            if (!string.IsNullOrEmpty(wait.Error))
            {
                logger.LogError(wait.Error);
                cb(null);
            }
            else
            {
                cb(wait.LoadedObject);
            }
            yield break;
        }
        private IEnumerator LoadTextureASync(AssetID assetid, Action<Texture> cb)
        {
            WaitLoadTexture waitLoadTexture = this.WaitLoadTexture(assetid, false);
            if (waitLoadTexture.keepWaiting)
            {
                yield return waitLoadTexture;
            }
            if (!string.IsNullOrEmpty(waitLoadTexture.Error))
            {
                logger.LogError(waitLoadTexture.Error);
                cb(null);
            }
            else
            {
                cb(waitLoadTexture.LoadedObject);
            }
            yield break;
        }


        private IEnumerator LoadTexture(AssetID assetid, Action<Texture> cb)
        {
            WaitLoadTexture waitLoadTexture = this.WaitLoadTexture(assetid, true);
            if (waitLoadTexture.keepWaiting)
            {
                yield return waitLoadTexture;
            }
            if (!string.IsNullOrEmpty(waitLoadTexture.Error))
            {
                TexturePool.logger.LogError(waitLoadTexture.Error);
                cb(null);
            }
            else
            {
                cb(waitLoadTexture.LoadedObject);
            }
            yield break;
        }

        private WaitLoadTexture WaitLoadTexture(AssetID assetid, bool isSync)
        {
            TextureCache cache;
            if (this.textureAssetIdMap.TryGetValue(assetid, out cache))
            {
                cache.Retain();
                return new WaitLoadTexture(cache);
            }

            cache = new TextureCache(assetid, this.textureMap);
            cache.IsSync = isSync;
            cache.Load(assetid);
            cache.Retain();
            this.textureAssetIdMap.Add(assetid, cache);
            return new WaitLoadTexture(cache);
        }



        private bool UnloadOnTime(AssetID assetid, TextureCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount > 0)
            {
                return false;
            }
            if (this.releaseCount < 3 && Time.time > cache.LastFreeTime + cache.ReleaseAfterFree)
            {
                this.releaseCount++;
                var obj = cache.GetObject();
                if (obj != null)
                {
                    this.textureMap.Remove(obj);
                    Resources.UnloadAsset(obj);
                    AssetManager.UnloadAsseBundle(assetid.BundleName);
                }
                return true;
            }
            return false;

        }

        private bool Unload(AssetID assetid, TextureCache cache)
        {
            if (!string.IsNullOrEmpty(cache.Error))
            {
                return true;
            }

            if (cache.ReferenceCount > 0)
            {
                return false;
            }

            var obj = cache.GetObject();
            if (obj != null)
            {
                Resources.UnloadAsset(obj);
                AssetManager.UnloadAsseBundle(assetid.BundleName);
                this.textureMap.Remove(obj);
            }
            return true;
        }
    }
}


